package com.app.utils.task;

import com.app.utils.base.j2se.Common;
import com.app.utils.base.j2se.Logger;
import com.app.utils.spring.properties.Config;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * 基于quartz框架的调度管理者
 * <br>支持热加载 [加载之前需先加载bean]
 * <br>支持多机部署 [以数据库时间为准的服务要求服务器和数据库时间一致]
 * <br>任务按名字和组管理 名字=iocBeanName 组=按是否多机分为两组 并增加描述记忆该任务内容
 * <br>任务的触发器按名字和组管理 名字=任务的组+任务名+Trigger 组=任务组
 */
@Configuration
public class TaskSetup implements Runnable {

	@Autowired
	private ApplicationContext applicationContext;
	@Autowired
    private TaskDao taskDao;
	/** 基于 quartz 的任务器调度器 */
	private static Scheduler scheduler;
	/** 正在执行的所有任务 **/
	static Map<String, Task> tasking = new HashMap<>();

	@PostConstruct
	public void init() {
		//启动之前需要统一时间
		//暂时不实现 因为多机并发算法不需要对时
		//只有以数据时间为准的情况 需要和数据库对时 让运维完成
		//初始化调度器
		if(null != scheduler){
			return;
		}
		initScheduler();
		//初始化任务并得到当前配置中所有任务
		initTask();
		//启动任务更新线程
		new Thread(this).start();
	}

	/**
	 * 初始化调度器
	 */
	private void initScheduler(){
		try {
			scheduler = StdSchedulerFactory.getDefaultScheduler();
			scheduler.start();
            Logger.info("初始化调度器scheduler完成");
		} catch (SchedulerException e) {
            Logger.error("初始化调度器scheduler失败", e);
			throw new RuntimeException("启动scheduler出错");
		}
	}

	@Override
	public void run() {
		while (true) {
			try {
				Thread.sleep(10000);
				//初始化任务并得到当前配置中所有任务
				Map<String, Task> task = initTask();
				//删除已经不再需要执行的任务
				deleteTask(task);
			} catch (Exception e) {
                Logger.error("定时任务线程出错", e);
			}
		}
	}

	/**
	 * 读取配置文件中任务列表
	 * <br>将没有运行的任务注册
	 * @return 本次读取到的所有任务
	 */
	private Map<String, Task> initTask(){
		//转化为任务模型列表
		List<Task> tasks = taskDao.tasks(Config.pp.get("service"));
		if(Common.isEmpty(tasks)){
			Logger.info("定时任务配置为空");
			return new HashMap<>();
		}
		//遍历检查配置是否完整
		Map<String, Task> taskMap = new HashMap<>();
		for(Task task : tasks){
			//检查配置
			if(!task.validate()){
                Logger.error(String.format("任务配置出错,jobName:%s,desc:%s",task.getJobName(),task.getDesc()),new RuntimeException("任务配置出错"));
				continue;
			}
			//检查是否已存在
			if(taskMap.containsKey(task.getJobName())){
                Logger.error(String.format("任务配置重复,jobName:%s,desc:%s",task.getJobName(),task.getDesc()),new RuntimeException("任务配置重复"));
				continue;
			}
			//检查bean是否存在
			if(!applicationContext.containsBean(task.getBeanName())){
                Logger.error(String.format("任务名对应bean不存在,jobName:%s,desc:%s",task.getJobName(),task.getDesc()),new RuntimeException("任务名对应bean不存在"));
				continue;
			}
			//现在没有执行该任务 注册任务
			if(!tasking.containsKey(task.getJobName())){
				registJob(task);
			}
			taskMap.put(task.getJobName(), task);
		}
		return taskMap;
	}

	/**
	 * 根据最新的任务配置删除当前不需要执行的任务
	 * @param tasks 从配置文件读取的最新的任务
	 */
	private void deleteTask(Map<String, Task> tasks){
		//当前没有任务
		if(tasking.isEmpty()){
			return;
		}
		Map<String, Task> old = new HashMap<>(tasking);
		old.forEach((key, task) -> {
            if (!tasks.containsKey(key)) {
                try {
                	tasking.remove(key);
                    scheduler.deleteJob(JobKey.jobKey(task.getJobName(), task.getJobGroup()));
                    Logger.info("任务删除成功,jobName:%s,desc:%s", task.getJobName(), task.getDesc());
                } catch (SchedulerException e) {
                    Logger.error(String.format("删除任务失败,jobName:%s,desc:%s", task.getJobName(), task.getDesc()), e);
                }
            }
        });
	}

	/**
	 * 注册任务
	 */
	private void registJob(Task task){
		try {
			//创建任务执行 单机设置redis
			task.setJob(applicationContext.getBean(task.getBeanName(),Job.class));
			//创建一个任务明细
            JobDetail jobDetail = JobBuilder.newJob(TaskLoader.class)
                    .withIdentity(task.getJobName(), task.getJobGroup())
                    .withDescription(task.getDesc())
                    .build();
			//创建一个任务触发器
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(task.triggerName(),task.getJobGroup())
                    .startNow()
                    .withSchedule(CronScheduleBuilder.cronSchedule(task.getCron()))
                    .build();
			//注册任务
			scheduler.scheduleJob(jobDetail, trigger);
			tasking.put(task.getJobName(), task);
            Logger.info("任务注册成功,jobName:%s,desc:%s",task.getJobName(),task.getDesc());
		} catch (Exception e) {
            Logger.error(String.format("注册任务失败,jobName:%s,desc:%s",task.getJobName(),task.getDesc()),e);
		}
	}

}
